﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Jacaranda.Data;
using System.Data;
using System.Runtime.Serialization;

namespace Jacaranda.Model
{
    [DataContract]
    public class UpdatableEntity : Entity
    {
        protected void Set(string propertyName, object value)
        {
            if (changedAssociationProperties == null)
            {
                changedAssociationProperties = new List<string>();
            }

            if (!changedAssociationProperties.Contains(propertyName))
            {
                changedAssociationProperties.Add(propertyName);
            }

            GetAssociationField(propertyName).SetAssociationsFromField(this, value);
        }

        public virtual void BeforeSave()
        {
            
        }

        public virtual void AfterSave()
        {
        }

        public void Save()
        {
            Save(true);
        }

        public void Save(bool saveAssociations)
        {
            BeforeSave();

            bool isInternalTransaction = false;
            Transaction tran = Transaction;

            if (tran == null)
            {
                tran = DataPortal.BeginTransaction();
                isInternalTransaction = true;
            }

            try
            {
                EntityMetadata m = Metadata.Type(GetType());

                DataTable existing = DataPortal.Select(string.Format("SELECT * FROM {0} WHERE {1}={2}",
                    m.TableName, m.DbPrimaryKey.FieldName,
                    m.DbPrimaryKey.GetValue(this)), tran);

                if (existing.Rows.Count == 0)
                {
                    existing.Rows.Add(existing.NewRow());
                }

                Mapper.SingleEntityToRelation(existing.Rows[0], this);
                DataPortal.Update(m.TableName, existing, tran);

                if (m.DbPrimaryKey != null)
                {
                    m.DbPrimaryKey.SetValue(this, existing.Rows[0][m.DbPrimaryKey.FieldName]);
                }

                if (saveAssociations && changedAssociationProperties != null)
                {
                    foreach (EntityMetadata inherited in Metadata.Type(GetType()).InheritanceChain)
                    {
                        foreach (AssociationField field in inherited.AssociationFields
                            .Where(i => changedAssociationProperties.Contains(i.PropertyName)))
                        {
                            field.Update(this, tran);
                        }
                    }
                }

                if (isInternalTransaction)
                {
                    tran.Commit();
                }

                loadedDependencies = null;
                changedAssociationProperties = null;

                AfterSave();
            }
            catch (Exception ex)
            {
                if (isInternalTransaction)
                {
                    tran.Rollback();
                }

                throw;
            }
        }


        public void RemoveAssociation<T>(T entity) where T : Entity
        {
            RemoveAssociation<T>(entity, null);
        }

        public void RemoveAssociation<T>(T entity, Transaction tran) where T : Entity
        {
            EntityAssociation association = Metadata.Associations.FirstOrDefault(i => i.From.Equals(GetType()) && i.To.Equals(typeof(T)));

            if (association != null)
            {
                association.Delete(this, entity, tran);
                return;
            }


            association = Metadata.Associations.FirstOrDefault(i => i.To.Equals(GetType()) && i.From.Equals(typeof(T)));
            association.Delete(entity, this, tran);
        }


        public void AddAssociation<T>(T entity) where T : Entity
        {
            AddAssociation<T>(entity, null);
        }

        public void AddAssociation<T>(T entity, Transaction tran) where T : Entity
        {
            EntityAssociation association = Metadata.Associations.FirstOrDefault(i => i.From.Equals(GetType()) && i.To.Equals(typeof(T)));

            if (association != null)
            {
                association.Insert(this, entity, tran);
                return;
            }


            association = Metadata.Associations.FirstOrDefault(i => i.To.Equals(GetType()) && i.From.Equals(typeof(T)));
            association.Insert(entity, this, tran);
        }
    }
}
